/*
 * Copyright (C) 2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"
#include "ewindow.h"

/* events currently handled */
#define event_mask()			(KeyPressMask		|	\
					 KeyReleaseMask		|	\
					 ButtonPressMask	|	\
					 ButtonReleaseMask	|	\
					 PointerMotionMask	|	\
					 ButtonMotionMask	|	\
					 EnterWindowMask	|	\
					 LeaveWindowMask	|	\
					 FocusChangeMask	|	\
					 ExposureMask)

#define default_font()			"sans serif:pixelsize=12"

/* common code for builtin operations; requires:
 *	o an evalue_t named value pointing to current builtin argument
 *	o an econtext_t pointer named context
 *	o an estate_t pointer named state
 *	o a Drawable variable named variable
 * if things go correctly, sets up the proper variables, otherwise, will
 * return an exception error code for the vm
 */
#define validate_context()						\
    do {								\
	if (value->t != t_context) {					\
	    thread_self->except = except_type_mismatch;			\
	    window_unlock();						\
	    return;							\
	}								\
	context = value->v.o;						\
	if ((state = context->__state) == null) {			\
	    thread_self->except = except_input_output_error;		\
	    window_unlock();						\
	    return;							\
	}								\
	drawable = context->drawable ?					\
	    context->drawable->__window : root_window->__window;	\
    } while (0)

#define window_lock()		emutex_lock(&window_mutex)
#define window_unlock()		emutex_unlock(&window_mutex)

/*
 * Prototypes
 */
static void
open_display(eint32_t count);
static void
close_display(eint32_t count);

static void
create_bitmap(eint32_t count);
static void
create_pixmap(eint32_t count);

static void
create_window(eint32_t count);
static void
change_window(eint32_t count);
static void
map_window(eint32_t count);
static void
unmap_window(eint32_t count);
static void
get_focus(eint32_t count);
static void
set_focus(eint32_t count);
static void
destroy_window(eint32_t count);
static void
destroy_subwindows(ewindow_t *window);

static void
select_input(eint32_t count);
static void
check_event(eint32_t count);
static void
next_event(eint32_t count);
static void
trans_coord(eint32_t count);
static void
trans_text(eint32_t count);
static ebool_t
translate_event(XEvent *xevent, eevent_t *event);
static euint32_t
translate_event_type(XEvent *xevent);
static euint32_t
translate_event_mask(euint32_t mask);

static void
create_context(eint32_t count);
static void
change_context(eint32_t count);
static void
destroy_context(eint32_t count);
static ebool_t
control_context(ebool_t create);

static void
copy_rect(eint32_t count);
static void
draw_line(eint32_t count);
static void
draw_rect(eint32_t count);
static void
fill_rect(eint32_t count);
static void
fill_poly(eint32_t count);
static void
draw_text(eint32_t count);
static void
text_rect(eint32_t count);
static void
clear_rect(eint32_t count);

static eint32_t
value_to_int32(evalue_t *value);
static void
pixel_to_color(euint32_t pixel, XRenderColor *color);

static void
create_timer(eint32_t count);
static void
change_timer(eint32_t count);
static void
destroy_timer(eint32_t count);
static ebool_t
remove_timer(etimer_t *timer);
static ebool_t
update_timer(struct timeval *now, etimer_t *timer);
static ebool_t
adjust_timers(struct timeval *now);
static ebool_t
timer_event(eevent_t *event, ebool_t block);

static void
fetch_bytes(eint32_t count);
static void
store_bytes(eint32_t count);

static void
grab_input(eint32_t count);

static void
ungrab_input(eint32_t count);

/*
 * Initialization
 */
static ewindow_t	*root_window;
static etimer_t		*root_timer;	/* first timer */
static etimer_t		*curr_timer;	/* current timer */
static etimer_t		*pend_timer;	/* first pending timer */
static etimer_t		*next_timer;	/* next timer to trigger */
static Display		*global_display;
static Screen		*global_screen;
static Visual		*global_visual;
static Colormap		 global_colormap;
static XContext		 window_context;
static XIM		 global_xim;
static Atom		 WM_PROTOCOLS;
static Atom		 WM_DELETE_WINDOW;
#if HAVE_THREAD
static pthread_mutex_t	 window_mutex;
#endif

/*
 * Implementation
 */
void
init_window(void)
{
    esymbol_t	*symbol;
    erecord_t	*record;
    evector_t	*vector;

    eadd_root((eobject_t *)&root_window);
    eadd_root((eobject_t *)&root_timer);

    XInitThreads();
    XSetLocaleModifiers("");
    window_context = XUniqueContext();

#define add_auto_field(name)						\
    do {								\
	vector = eget_string((euint8_t *)name, strlen(name));		\
	enew_field(record, vector, null);				\
    } while (0)
#define add_type_field(type, name)					\
    do {								\
	vector = eget_string((euint8_t *)type, strlen(type));		\
	symbol = eget_symbol(ht_language, vector);			\
	vector = eget_string((euint8_t *)name, strlen(name));		\
	enew_field(record, vector, symbol->tag);			\
    } while (0)
    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"bitmap_t", 8));
    record = enew_record(symbol, t_struct);
    /* private */
#if __WORDSIZE == 32
    add_type_field("uint32_t",	"*bitmap*");
#else
    add_type_field("uint64_t",	"*bitmap*");
#endif
    /* public */
    add_type_field("uint16_t",	"width");
    add_type_field("uint16_t",	"height");
    add_type_field("string_t",	"data");
    /* private */
    add_type_field("uint16_t",	"*width*");
    add_type_field("uint16_t",	"*height*");
    add_type_field("string_t",	"*data*");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"pixmap_t", 8));
    record = enew_record(symbol, t_struct);
    /* private */
#if __WORDSIZE == 32
    add_type_field("uint32_t",	"*pixmap*");
#else
    add_type_field("uint64_t",	"*pixmap*");
#endif
    /* public */
    add_type_field("uint16_t",	"width");
    add_type_field("uint16_t",	"height");
    /* private */
    add_type_field("uint16_t",	"*width*");
    add_type_field("uint16_t",	"*height*");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"window_t", 8));
    record = enew_record(symbol, t_struct);
    /* private */
#if __WORDSIZE == 32
    add_type_field("uint32_t",	"*window*");
#else
    add_type_field("uint64_t",	"*window*");
#endif
    /* public */
    add_type_field("uint16_t",	"width");
    add_type_field("uint16_t",	"height");
    add_type_field("int16_t",	"x");
    add_type_field("int16_t",	"y");
    add_type_field("uint32_t",	"pixel");
    add_type_field("pixmap_t",	"pixmap");
    add_type_field("uint8_t",	"popup");
    add_type_field("uint8_t",	"input");
    add_type_field("uint32_t",	"events");
    add_type_field("window_t",	"parent");
    /* private */
    add_type_field("uint16_t",	"*width*");
    add_type_field("uint16_t",	"*height*");
    add_type_field("int16_t",	"*x*");
    add_type_field("int16_t",	"*y*");
    add_type_field("uint32_t",	"*pixel*");
    add_type_field("pixmap_t",	"*pixmap*");
    add_type_field("uint8_t",	"*popup*");
    add_type_field("uint8_t",	"*input*");
    add_type_field("uint32_t",	"*events*");
    add_type_field("window_t",	"*parent*");
    add_type_field("window_t",	"*children*");
    add_type_field("window_t",	"*prev*");
    add_type_field("window_t",	"*next*");
    add_auto_field(		"*xic*");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"event_t", 7));
    record = enew_record(symbol, t_struct);
    /* public */
    add_type_field("window_t",	"window");
    add_type_field("uint32_t",	"type");
    add_type_field("uint16_t",	"width");
    add_type_field("uint16_t",	"height");
    add_type_field("int16_t",	"x");
    add_type_field("int16_t",	"y");
    add_type_field("uint32_t",	"state");
    add_type_field("uint32_t",	"detail");
    /* private */
    add_auto_field(		"*event*");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"context_t", 9));
    record = enew_record(symbol, t_struct);
    /* private */
    add_auto_field(		"*state*");
    /* public */
    add_auto_field(		"drawable");
    add_type_field("string_t",	"font");
    add_type_field("uint32_t",	"background");
    add_type_field("uint32_t",	"foreground");
    add_type_field("uint16_t",	"line_width");
    /* private */
    add_auto_field(		"*drawable*");
    add_type_field("string_t",	"*font*");
    add_type_field("uint32_t",	"*background*");
    add_type_field("uint32_t",	"*foreground*");
    add_type_field("uint16_t",	"*line_width*");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"timer_t", 7));
    record = enew_record(symbol, t_struct);
    /* private */
    add_type_field("timer_t",	"*next*");
    add_type_field("timer_t",	"*prev*");
    /* public */
    add_type_field("window_t",	"window");
    add_type_field("uint32_t",	"msec");
    add_type_field("uint8_t",	"repeat");
    add_type_field("uint8_t",	"pending");
    /* private */
    add_type_field("window_t",	"*window*");
    add_type_field("uint32_t",	"*msec*");
    add_type_field("uint8_t",	"*repeat*");
    /* must match (at least) the size of struct timeval */
#if __WORDSIZE == 32
    add_type_field("uint32_t",	"*sec*");
    add_type_field("uint32_t",	"*usec*");
#else
    add_type_field("uint64_t",	"*sec*");
    add_type_field("uint64_t",	"*usec*");
#endif
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"point_t", 7));
    record = enew_record(symbol, t_struct);
    add_type_field("int16_t",	"x");
    add_type_field("int16_t",	"y");
    eend_record(record);

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"rect_t", 6));
    record = enew_record(symbol, t_struct);
    add_type_field("uint16_t",	"width");
    add_type_field("uint16_t",	"height");
    add_type_field("int16_t",	"x");
    add_type_field("int16_t",	"y");
    eend_record(record);
#undef add_type_field
#undef add_auto_field

#define add_builtin(name, count)					\
    do {								\
	vector = eget_string((euint8_t *)#name, strlen(#name));		\
	symbol = enew_symbol(ht_language, vector);			\
	symbol = enew_bound_symbol(symbol);				\
	edefine_builtin(symbol, name, count, false);			\
    } while (0)
#define add_varargs(name, count)					\
    do {								\
	vector = eget_string((euint8_t *)#name, strlen(#name));		\
	symbol = enew_symbol(ht_language, vector);			\
	symbol = enew_bound_symbol(symbol);				\
	edefine_builtin(symbol, name, count, true);			\
    } while (0)

    add_builtin(open_display, 0);
    add_builtin(close_display, 0);

    add_builtin(create_bitmap, 1);
    add_builtin(create_pixmap, 1);

    add_builtin(create_window, 1);
    add_builtin(change_window, 1);
    add_builtin(map_window, 1);
    add_builtin(unmap_window, 1);
    add_builtin(get_focus, 0);
    add_builtin(set_focus, 1);
    add_builtin(destroy_window, 1);

    add_builtin(select_input, 1);
    add_builtin(check_event, 1);
    add_builtin(next_event, 1);
    add_builtin(trans_coord, 5);
    add_builtin(trans_text, 3);

    add_builtin(create_context, 1);
    add_builtin(change_context, 1);
    add_builtin(destroy_context, 1);

    add_builtin(copy_rect, 8);
    add_builtin(draw_line, 5);
    add_builtin(draw_rect, 5);
    add_builtin(fill_rect, 5);
    add_builtin(fill_poly, 2);
    add_varargs(draw_text, 4);
    add_varargs(text_rect, 2);
    add_builtin(clear_rect, 6);

    add_builtin(create_timer, 1);
    add_builtin(change_timer, 1);
    add_builtin(destroy_timer, 1);

    add_builtin(fetch_bytes, 0);
    add_varargs(store_bytes, 1);

    add_builtin(grab_input, 1);
    add_builtin(ungrab_input, 0);
#undef add_varargs
#undef add_builtin

    emutex_init(&window_mutex);
}

ebool_t
edestroy_pixmap(epixmap_t *pixmap)
{
    if (pixmap->__pixmap) {
	XFreePixmap(global_display, pixmap->__pixmap);
	/* in case there is still some reference */
	memset(pixmap, 0, sizeof(epixmap_t));
    }
    return (false);
}

ebool_t
edestroy_window(ewindow_t *window)
{
    ebool_t	result;

    if (window->__next == window) {
	if (window->__parent)
	    window->__parent->__children = null;
    }
    else if (window->__next) {
	window->__next->__prev = window->__prev;
	window->__prev->__next = window->__next;
    }

    if (window->__window) {
	XDeleteContext(global_display, window->__window, window_context);
	XDestroyWindow(global_display, window->__window);
	if (window->__xic)
	    XDestroyIC(*window->__xic);
	destroy_subwindows(window);
	result = true;
    }
    else
	result = false;

    /* in case there is still some reference */
    memset(window, 0, sizeof(ewindow_t));
    return (result);
}

ebool_t
edestroy_context(econtext_t *context)
{
    estate_t	*state;

    if ((state = context->__state)) {
	if (state->gc)
	    XFreeGC(global_display, state->gc);
	if (state->draw)
	    XftDrawDestroy(state->draw);
	if (state->font)
	    XftFontClose(global_display, state->font);
	context->__state = null;
	return (true);
    }
    return (false);
}

/***********************************************************************
 * Display operations
 ***********************************************************************/
static void
open_display(eint32_t count)
/*
 *	bool open_display(void)
 */
{
    thread_self->ret.t = t_int;

    window_lock();
    if (!global_display) {
	global_display = XOpenDisplay(null);
	if (!global_display) {
	    thread_self->except = except_input_output_error;
	    window_unlock();
	    return;
	}
	/* fast/simple access to globals */
	global_screen		= ScreenOfDisplay(global_display,
						  DefaultScreen(global_display));
	global_visual		= DefaultVisualOfScreen(global_screen);
	global_colormap		= DefaultColormapOfScreen(global_screen);
	enew_object((eobject_t *)&root_window, t_window, sizeof(ewindow_t));
	root_window->__width	= root_window->width =
	    WidthOfScreen(global_screen);
	root_window->__height	= root_window->height =
	    HeightOfScreen(global_screen);
	root_window->__window	= RootWindowOfScreen(global_screen);
	XSaveContext(global_display, root_window->__window,
		     window_context, (XPointer)root_window);
	global_xim = XOpenIM(global_display, null, null, null);
	WM_PROTOCOLS = XInternAtom(global_display, "WM_PROTOCOLS", False);
	WM_DELETE_WINDOW = XInternAtom(global_display, "WM_DELETE_WINDOW", False);
	thread_self->ret.v.i	= true;
    }

    thread_self->ret.v.i = false;
    window_unlock();
}

static void
close_display(eint32_t count)
/*
 *	bool close_display(void)
 */
{
    window_lock();
    thread_self->ret.t = t_int;
    if (global_display) {
	if (global_xim) {
	    XCloseIM(global_xim);
	    global_xim		= null;
	}
	edestroy_window(root_window);
	XCloseDisplay(global_display);
	global_display		= null;
	root_window		= null;
	thread_self->ret.v.i	= true;
    }
    thread_self->ret.v.i = false;
    window_unlock();
}


/***********************************************************************
 * Pixmap operations
 ***********************************************************************/
/*
 *	void create_bitmap(bitmap_t)
 */
static void
create_bitmap(eint32_t count)
{
    ebitmap_t	*bitmap;

    window_lock();
    if (thread_self->esp[-1].t != t_bitmap) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    bitmap = thread_self->esp[-1].v.o;
    if (bitmap->__bitmap) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    bitmap->__width	= bitmap->width;
    bitmap->__height	= bitmap->height;
    bitmap->__data	= bitmap->data;
    if (bitmap->data) {
	if (((bitmap->width + 7) >> 3) * bitmap->height > bitmap->data->length) {
	    thread_self->except = except_invalid_argument;
	    window_unlock();
	    return;
	}
	bitmap->__bitmap = XCreateBitmapFromData(global_display,
						 root_window->__window,
						 (char *)bitmap->__data->v.obj,
						 bitmap->__width,
						 bitmap->__height);
    }
    else
	bitmap->__bitmap = XCreatePixmap(global_display,
					 root_window->__window,
					 bitmap->__width,
					 bitmap->__height,
					 1);
    thread_self->ret.t = t_void;
    window_unlock();
}

/*
 *	void create_pixmap(pixmap_t)
 */
static void
create_pixmap(eint32_t count)
{
    epixmap_t	*pixmap;

    window_lock();
    if (thread_self->esp[-1].t != t_pixmap) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    pixmap = thread_self->esp[-1].v.o;
    if (pixmap->__pixmap) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    pixmap->__width	= pixmap->width;
    pixmap->__height	= pixmap->height;
    pixmap->__pixmap = XCreatePixmap(global_display,
				     root_window->__window,
				     pixmap->__width,
				     pixmap->__height,
				     DefaultDepthOfScreen(global_screen));
    thread_self->ret.t = t_void;
    window_unlock();
}

/***********************************************************************
 * Window operations
 ***********************************************************************/
static void
create_window(eint32_t count)
/*
 *	bool create_window(window_t)
 */
{
    unsigned long	 mask;
    euint32_t		 events;
    ewindow_t		*window;
    XSetWindowAttributes attributes;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	/* argument must be a window_t */
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }

    thread_self->ret.t = t_int;
    window = thread_self->esp[-1].v.o;
    if (window->__window) {
	/* window already created */
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }
    thread_self->ret.v.i = true;

    if (window->parent == null) {
	if (root_window == null)
	    open_display(0);
	window->parent = root_window;
    }
    else if (eobject_type(window->parent) != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window->__parent = window->parent;

    mask = 0;
    window->__pixel	= window->pixel;
    window->__pixmap	= window->pixmap;
    if (window->__pixmap && window->__pixmap->__pixmap) {
	mask |= CWBackPixmap;
	attributes.background_pixmap = window->__pixmap->__pixmap;
    }
    else {
	mask |= CWBackPixel;
	attributes.background_pixel = window->__pixel;
    }
    window->__popup	= window->popup;
    if (window->__popup) {
	mask |= CWOverrideRedirect;
	attributes.override_redirect = true;
    }
    window->__events	= window->events;
    events = translate_event_mask(window->__events);
    if (events) {
	mask |= CWEventMask;
	attributes.event_mask = events;
    }

    window->__width	= window->width;
    window->__height	= window->height;
    window->__x		= window->x;
    window->__y		= window->y;
    window->__window = XCreateWindow(global_display,
				     window->__parent->__window,
				     window->__x, window->__y,
				     window->__width, window->__height,
				     0, CopyFromParent,
				     InputOutput, (Visual *)CopyFromParent,
				     mask, &attributes);
    /* Only setable at create time */
    if (window->__parent == root_window &&
	(window->__events & (1 << eprotocol_event)))
	XSetWMProtocols(global_display, window->__window, &WM_DELETE_WINDOW, 1);

    if (window->__parent->__children == null)
	window->__parent->__children = window->__prev = window->__next = window;
    else {
	window->__prev = window->__parent->__children->__prev;
	window->__parent->__children->__prev->__next = window;
	window->__parent->__children->__prev = window;
	window->__next = window->__parent->__children;
    }

    window->__input = window->input;

    XSaveContext(global_display, window->__window,
		 window_context, (XPointer)window);
    window_unlock();
}

static void
change_window(eint32_t count)
/*
 *	bool change_window(window_t)
 */
{
    unsigned long	 mask;
    unsigned long	 mrmask;
    euint32_t		 events;
    ewindow_t		*window;
    XSetWindowAttributes attributes;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;

    thread_self->ret.t = t_int;
    if (window == root_window) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    /* FIXME handle:
     *	reparent
     */

    mask = 0;
    if (window->__pixmap != window->pixmap) {
	window->__pixmap = window->pixmap;
	mask |= CWBackPixmap;
	attributes.background_pixmap = window->__pixmap->__pixmap;
    }
    else if (window->__pixel != window->pixel) {
	window->__pixel = window->pixel;
	mask |= CWBackPixel;
	attributes.background_pixel = window->pixel;
    }
    if (!!window->__popup != !!window->popup) {
	window->__popup = window->popup;
	mask |= CWOverrideRedirect;
	attributes.override_redirect = window->__popup;
    }
    if (window->__events != window->events) {
	window->__events = window->events;
	events = translate_event_mask(window->__events);
	mask |= CWEventMask;
	attributes.event_mask = events;
    }

    mrmask = 0;
    if (window->__x != window->x) {
	window->__x = window->x;
	mrmask |= CWX;
    }
    if (window->__y != window->y) {
	window->__y = window->y;
	mrmask |= CWY;
    }
    if (window->__width != window->width) {
	window->__width = window->width;
	mrmask |= CWWidth;
    }
    if (window->__height != window->height) {
	window->__height = window->height;
	mrmask |= CWHeight;
    }
    window->__input = window->input;

    thread_self->ret.v.i = mask || mrmask;
    if (window->__window) {
	if (mask)
	    XChangeWindowAttributes(global_display, window->__window,
				    mask, &attributes);
	if (mrmask)
	    XMoveResizeWindow(global_display, window->__window,
			      window->__x, window->__y,
			      window->__width, window->__height);
	if (thread_self->ret.v.i)
	    XClearArea(global_display, window->__window, 0, 0,
		       window->__width, window->__height, True);	
    }
    window_unlock();
}

static void
map_window(eint32_t count)
/*
 *	bool map_window(window_t)
 */
{
    ewindow_t	*window;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    if (window == root_window || window->__window == None) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    if (window->__popup)
	XMapRaised(global_display, window->__window);
    else
	XMapWindow(global_display, window->__window);
    
    thread_self->ret.v.i = true;
    window_unlock();
}

static void
unmap_window(eint32_t count)
/*
 *	bool unmap_window(window_t)
 */
{
    ewindow_t	*window;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    if (window == root_window || window->__window == None) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    XUnmapWindow(global_display, window->__window);
    thread_self->ret.v.i = true;
    window_unlock();
}

static void
get_focus(eint32_t count)
/*
 *	window_t get_focus()
 */
{
    eint32_t	revert;
    Window	window;

    window_lock();
    XGetInputFocus(global_display, &window, &revert);
    XFindContext(global_display, window, window_context,
		 (XPointer *)&thread_self->ret.v.o);
    if (thread_self->ret.v.o == root_window)
	thread_self->ret.v.o = null;
    thread_self->ret.t = thread_self->ret.v.o ? t_window : t_void;
    window_unlock();
}

static void
set_focus(eint32_t count)
/*
 *	bool set_focus(window_t)
 */
{
    ewindow_t	*window;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    if (window == root_window || window->__window == None) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    XSetInputFocus(global_display, window->__window, RevertToNone, CurrentTime);
    thread_self->ret.v.i = true;
    window_unlock();
}

static void
destroy_window(eint32_t count)
/*
 *	bool destroy_window(window_t)
 */
{
    ewindow_t	*window;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    if (window == root_window || window->__window == None) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    thread_self->ret.v.i = edestroy_window(window);
    window_unlock();
}

static void
destroy_subwindows(ewindow_t *window)
{
    ewindow_t	*next;
    ewindow_t	*child;

    if (window->__children) {
	child = window->__children;
	do {
	    next = child->__next;
	    XDeleteContext(global_display, child->__window, window_context);
	    destroy_subwindows(child);
	    memset(child, 0, sizeof(ewindow_t));
	    child = next;
	} while (child != window->__children);
	window->__children = null;
    }
}

/***********************************************************************
 * Event operations
 ***********************************************************************/
static void
select_input(eint32_t count)
/*
 *	bool select_input(window_t)
 */
{
    euint32_t	 events;
    ewindow_t	*window;

    window_lock();
    if (thread_self->esp[-1].t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    if (window == root_window || window->__window == None) {
	thread_self->ret.v.i = false;
	window_unlock();
	return;
    }

    if (window->__events != window->events) {
	window->__events = window->events;
	events = translate_event_mask(window->__events);
	XSelectInput(global_display, window->__window, events);
	thread_self->ret.v.i = true;
    }
    else
	thread_self->ret.v.i = false;
    window_unlock();
}

static void
check_event(eint32_t count)
/*
 *	event_t check_event(event_t)
 */
{
    eevent_t	*event;
    eevent_t	 sevent;
    XEvent	 xevent;

    window_lock();
    switch (thread_self->esp[-1].t) {
	case t_void:
	    event = &sevent;
	    break;
	case t_event:
	    event = thread_self->esp[-1].v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    /* if no timer pending */
    if (!timer_event(event, false)) {
	/* if no event pending */
	if (!XCheckMaskEvent(global_display, event_mask(), &xevent) ||
	    /* if event was discarded */
	    !translate_event(&xevent, event)) {
	    thread_self->ret.t = t_void;
	    thread_self->ret.v.o = null;
	}
    }

    if (event == &sevent) {
	enew_object(&thread_self->obj, t_event, sizeof(eevent_t));
	event = thread_self->obj;
	memcpy(event, &sevent, sizeof(eevent_t));
    }

    thread_self->ret.t = t_event;
    thread_self->ret.v.o = event;
    window_unlock();
}

static void
next_event(eint32_t count)
/*
 *	event_t next_event(event_t)
 */
{
    eevent_t	*event;
    XEvent	 xevent;

    window_lock();
    switch (thread_self->esp[-1].t) {
	case t_void:
	    enew_object(&thread_self->obj, t_event, sizeof(eevent_t));
	    event = thread_self->obj;
	    break;
	case t_event:
	    event = thread_self->esp[-1].v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    do {
	if (timer_event(event, true))
	    break;
	XNextEvent(global_display, &xevent);
    } while (!translate_event(&xevent, event));

    thread_self->ret.t = t_event;
    thread_self->ret.v.o = event;
    window_unlock();
}

static void
trans_coord(eint32_t count)
/*
 *	point_t trans_coord(window_t, window_t, x, y, point_t)
 */
{
    ewindow_t	*src;
    ewindow_t	*dst;
    eint32_t	 srcx;
    eint32_t	 srcy;
    eint32_t	 dstx;
    eint32_t	 dsty;
    Window	 temp;
    epoint_t	*point;
    evalue_t	*value;

    window_lock();
    value = thread_self->esp - 1;
    switch (value->t) {
	case t_void:
	    src = root_window;
	    break;
	case t_window:
	    src = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    switch ((--value)->t) {
	case t_void:
	    dst = root_window;
	    break;
	case t_window:
	    dst = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    srcx = value_to_int32(--value);
    srcy = value_to_int32(--value);
    switch ((--value)->t) {
	case t_void:
	    enew_object(&thread_self->obj, t_point, sizeof(epoint_t));
	    point = thread_self->obj;
	    break;
	case t_point:
	    point = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    XTranslateCoordinates(global_display, src->__window, dst->__window,
			  srcx, srcy, &dstx, &dsty, &temp);
    point->x = dstx;
    point->y = dsty;
    thread_self->ret.t = t_point;
    thread_self->ret.v.o = point;
    window_unlock();
}

static void
trans_text(eint32_t count)
/*
 *	uint32_t trans_text(window_t, event_t, string_t)
 */
{
    eevent_t		*event;
    evalue_t		*value;
    eint32_t		 length;
    KeySym		 keysym;
    evector_t		*vector;
    ewindow_t		*window;
    char		 buffer[64];

    window_lock();
    value = thread_self->esp - 1;
    switch (value->t) {
	case t_void:
	    window = root_window;
	    break;
	case t_window:
	    window = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    switch ((--value)->t) {
	case t_void:
	    thread_self->except = except_null_dereference;
	    window_unlock();
	    return;
	case t_event:
	    event = value->v.o;
	    if (event->type != ekey_press_event) {
		thread_self->except = except_invalid_argument;
		window_unlock();
		return;
	    }
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    switch ((--value)->t) {
	case t_void:
	    vector = null;
	    break;
	case t_string:
	    vector = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    if (window->__xic)
	length = Xutf8LookupString(*window->__xic, &event->__event->xkey,
				   buffer, sizeof(buffer) - 1, &keysym, null);
    else
	length = XLookupString(&event->__event->xkey,
			       buffer, sizeof(buffer) - 1, &keysym, null);
    if (vector) {
	erenew_vector(vector, length);
	memcpy(vector->v.obj, buffer, length);
    }
    thread_self->ret.t = t_int;
    thread_self->ret.v.i = keysym;
    window_unlock();
}

static ebool_t
translate_event(XEvent *xevent, eevent_t *event)
{
    ewindow_t	*window;

    /* Let input method check event */
    if (XFilterEvent(xevent, None))
        return (false);

    XFindContext(global_display, xevent->xany.window,
		 window_context, (XPointer *)&event->window);
    event->type = translate_event_type(xevent);
    switch (xevent->type) {
	case KeyPress:
	case KeyRelease:
	case ButtonPress:
	case ButtonRelease:
	    event->x	  = xevent->xkey.x;
	    event->y	  = xevent->xkey.y;
	    event->state  = xevent->xkey.state;
	    event->detail = xevent->xkey.keycode;
	    event->width  = 0;
	    event->height = 0;
	    break;
	case MotionNotify:
	    event->x	  = xevent->xmotion.x;
	    event->y	  = xevent->xmotion.y;
	    event->state  = xevent->xmotion.state;
	    event->detail = 0;
	    event->width  = 0;
	    event->height = 0;
	    break;
	case EnterNotify:
	case LeaveNotify:
	    event->x	  = xevent->xcrossing.x;
	    event->y	  = xevent->xcrossing.y;
	    event->state  = 0;
	    event->detail = 0;
	    event->width  = 0;
	    event->height = 0;
	    break;
	case FocusIn:
	    window = event->window;
	    if (window && window->input && global_xim && !window->__xic) {
		XIC	xic;

		xic = XCreateIC(global_xim,
				XNClientWindow,
				window->__window,
				XNInputStyle,
				XIMPreeditNothing | XIMStatusNothing,
				null, null);
		if (xic) {
		    enew_object((eobject_t *)&window->__xic,
				t_void, sizeof(eobject_t));
		    *window->__xic = xic;
		}
	    }
	    if (window && window->__xic) {
		XSetICFocus(*window->__xic);
		XSetICValues(*window->__xic, XNFocusWindow,
			     window->__window, null);
	    }
	    event->x	  = 0;
	    event->y	  = 0;
	    event->state  = 0;
	    event->detail = 0;
	    event->width  = 0;
	    event->height = 0;
	    break;
	case FocusOut:
	    if (event->window && event->window->__xic)
		XUnsetICFocus(*event->window->__xic);
	case MapNotify:
	case UnmapNotify:
	    event->x	  = 0;
	    event->y	  = 0;
	    event->state  = 0;
	    event->detail = 0;
	    event->width  = 0;
	    event->height = 0;
	    break;
	case Expose:
	    if (xevent->xexpose.count)
		return (false);
	    event->x	  = xevent->xexpose.x;
	    event->y	  = xevent->xexpose.y;
	    event->state  = 0;
	    event->detail = 0;
	    event->width  = xevent->xexpose.width;
	    event->height = xevent->xexpose.height;
	    break;
	case ConfigureNotify:
	    event->x	  = xevent->xconfigure.x;
	    event->y	  = xevent->xconfigure.y;
	    event->state  = 0;
	    event->detail = 0;
	    event->width  = xevent->xconfigure.width;
	    event->height = xevent->xconfigure.height;
	    if (event->window) {
		event->window->__x	= event->window->x	= event->x;
		event->window->__y	= event->window->y	= event->y;
		event->window->__width	= event->window->width	= event->width;
		event->window->__height	= event->window->height	= event->height;
	    }
	    break;
	case ClientMessage:
	    if (xevent->xclient.message_type == WM_PROTOCOLS &&
		xevent->xclient.data.l[0] == WM_DELETE_WINDOW) {
		event->x = event->y = 0;
		event->width = event->height = 0;
		event->state = edelete_window_protocol;
		event->detail = 0;
	    }
	    else
		return (false);
	    break;
	case ReparentNotify:
	default:
	    return (false);
    }

    /* Keep a private copy; required at least by X{utf8,}LookupString */
    if (event->__event == null)
	enew_object((eobject_t *)&event->__event, t_void, sizeof(XEvent));
    memcpy(event->__event, xevent, sizeof(XEvent));

    return (true);
}

static euint32_t
translate_event_type(XEvent *event)
{
    switch (event->type) {
	case KeyPress:
	    return (ekey_press_event);
	case KeyRelease:
	    return (ekey_release_event);
	case ButtonPress:
	    return (ebutton_press_event);
	case ButtonRelease:
	    return (ebutton_release_event);
	case MotionNotify:
	    return (event->xmotion.state & (Button1Mask|
					    Button2Mask|
					    Button3Mask|
					    Button4Mask|
					    Button5Mask) ?
		    ebutton_motion_event : epointer_motion_event);
	case EnterNotify:
	    return (eenter_event);
	case LeaveNotify:
	    return (eleave_event);
	case FocusIn:
	    return (efocus_in_event);
	case FocusOut:
	    return (efocus_out_event);
	case ConfigureNotify:
	    return (eresize_event);
	case Expose:
	    return (eexpose_event);
	case MapNotify:
	    return (emap_event);
	case UnmapNotify:
	    return (eunmap_event);
	case ClientMessage:
	    return (eprotocol_event);
	default:
	    return (eerror_event);
    }
}

static euint32_t
translate_event_mask(euint32_t mask)
{
    euint32_t	result;

    result = 0;
    if (mask & (1 << ekey_press_event))
	result |= KeyPressMask;
    if (mask & (1 << ekey_release_event))
	result |= KeyReleaseMask;
    if (mask & (1 << ebutton_press_event))
	result |= ButtonPressMask;
    if (mask & (1 << ebutton_release_event))
	result |= ButtonReleaseMask;
    if (mask & (1 << epointer_motion_event))
	result |= PointerMotionMask;
    if (mask & (1 << ebutton_motion_event))
	result |= ButtonMotionMask;
    if (mask & (1 << eenter_event))
	result |= EnterWindowMask;
    if (mask & (1 << eleave_event))
	result |= LeaveWindowMask;
    if (mask & (1 << (efocus_in_event | efocus_out_event)))
	result |= FocusChangeMask;
    if (mask & (1 << eresize_event))
	result |= StructureNotifyMask;
    if (mask & (1 << eexpose_event))
	result |= ExposureMask;
    if (mask & (1 << (emap_event | eunmap_event)))
	result |= VisibilityChangeMask;

    return (result);
}

/***********************************************************************
 * Context operations
 ***********************************************************************/
static void
create_context(eint32_t count)
/*
 *	void create_context(context_t)
 */
{
    window_lock();
    control_context(true);
    window_unlock();
}

static void
change_context(eint32_t count)
/*
 *	void change_context(context_t)
 */
{
    window_lock();
    control_context(false);
    window_unlock();
}

static void
destroy_context(eint32_t count)
/*
 *	bool destroy_context(context_t)
 */
{
    econtext_t	*context;

    window_lock();
    if (thread_self->esp[-1].t != t_context) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    context = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    thread_self->ret.v.i = edestroy_context(context);
    window_unlock();
}

static ebool_t
control_context(ebool_t create)
{
    unsigned long	 mask;
    XRenderColor	 color;
    evalue_t		*value;
    estate_t		*state;
    eint32_t		 length;
    evector_t		*vector;
    econtext_t		*context;
    XGCValues		 xvalues;
    Drawable		 drawable;
    char		*fontspec;
    char		 buffer[BUFSIZ];

    value = thread_self->esp - 1;
    if (value->t == t_void) {
	thread_self->except = except_null_dereference;
	return (false);
    }
    if (value->t != t_context) {
	thread_self->except = except_type_mismatch;
	return (false);
    }
    context = value->v.o;

    if (context->drawable == null)
	drawable = root_window->__window;
    else {
	switch (eobject_type(context->drawable)) {
	    case t_pixmap:	case t_window:
		drawable = context->drawable->__window;
		break;
	    default:
		thread_self->except = except_type_mismatch;
		return (false);
	}
    }

    if (context->font == null)
	fontspec = default_font();
    else {
	switch (eobject_type(context->font) & ~t_const) {
	    case t_vector|t_int8:
	    case t_vector|t_uint8:
		vector = context->font;
		length = vector->length;
#if DEBUG
		if (length < 0)
		    /* should never happen */
		    length = 0;
#endif
		if (length >= BUFSIZ)
		    length = BUFSIZ - 1;
		memcpy(buffer, vector->v.obj, length);
		buffer[length] = '\0';
		fontspec = buffer;
		break;
	    default:
		thread_self->except = except_type_mismatch;
		return (false);
	}
    }

    if (create) {
	if (context->__state) {
	    thread_self->except = except_invalid_argument;
	    return (false);
	}
	enew_object((eobject_t *)&context->__state, t_void, sizeof(estate_t));
    }
    state = context->__state;

    if (create) {
	mask = GCFunction;
	xvalues.function = GXcopy;
    }
    else
	mask = 0;
    if (create || context->__background != context->background) {
	context->__background = context->background;
	pixel_to_color(context->__background, &color);
	XftColorAllocValue(global_display, global_visual, global_colormap,
			   &color, &state->background);
	mask |= GCBackground;
	xvalues.background	= state->background.pixel;
    }
    if (create || context->__foreground != context->foreground) {
	context->__foreground = context->foreground;
	pixel_to_color(context->__foreground, &color);
	XftColorAllocValue(global_display, global_visual, global_colormap,
			   &color, &state->foreground);
	mask |= GCForeground;
	xvalues.foreground	= state->foreground.pixel;
    }
    if (create || context->__line_width != context->line_width) {
	context->__line_width = context->line_width;
	mask |= GCLineWidth;
	xvalues.line_width	= context->__line_width;
    }
    if (create || context->__font != context->font) {
	context->__font = context->font;
	if (!create)
	    XftFontClose(global_display, state->font);
	state->font = XftFontOpenName(global_display,
				      DefaultScreen(global_display),
				      fontspec);
	if (state->font == null && strcmp(fontspec, default_font()))
	    state->font = XftFontOpenName(global_display,
					  DefaultScreen(global_display),
					  default_font());
	if (state->font == null) {
	    edestroy_context(context);
	    thread_self->except = except_input_output_error;
	    return (false);
	}
    }
    if (create) {
	state->gc = XCreateGC(global_display, drawable, mask, &xvalues);
	state->draw = XftDrawCreate(global_display, drawable,
				    global_visual, global_colormap);
	if (state->gc == null || state->draw == null) {
	    edestroy_context(context);
	    thread_self->except = except_input_output_error;
	    return (false);
	}
    }
    else {
	if (mask)
	    XChangeGC(global_display, state->gc, mask, &xvalues);
	if (context->__drawable != context->drawable) {
	    context->__drawable = context->drawable;
	    XftDrawChange(state->draw, drawable);
	}
    }

    thread_self->ret.t = t_void;
    return (true);
}

/***********************************************************************
 * Drawing operations
 ***********************************************************************/
static void
copy_rect(eint32_t count)
/*
 *	void copy_area(context_t, dx, dy, drawable, x, y, width, height)
 */
{
    eint32_t	 x;
    eint32_t	 y;
    eint32_t	 dx;
    eint32_t	 dy;
    euint32_t	 width;
    euint32_t	 height;
    estate_t	*state;
    evalue_t	*value;
    econtext_t	*context;
    Drawable	 drawable;
    Drawable	 source;
    ebool_t	 plane;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();
    dx		= value_to_int32(--value);
    dy		= value_to_int32(--value);
    switch ((--value)->t) {
	case t_bitmap:
	    plane = true;
	    break;
	case t_pixmap:
	case t_window:
	    plane = false;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }
    source	= ((ewindow_t *)value->v.o)->__window;
    x		= value_to_int32(--value);
    y		= value_to_int32(--value);
    width	= value_to_int32(--value);
    height	= value_to_int32(--value);

    if (plane)
	XCopyPlane(global_display, source, drawable, state->gc,
		   x, y, width, height, dx, dy, 1);
    else
	XCopyArea (global_display, source, drawable, state->gc,
		   x, y, width, height, dx, dy);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
draw_line(eint32_t count)
/*
 *	void draw_line(context_t, x1, y1, x2, y2)
 */
{
    eint32_t	 x1;
    eint32_t	 y1;
    eint32_t	 x2;
    eint32_t	 y2;
    estate_t	*state;
    evalue_t	*value;
    econtext_t	*context;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();

    x1 = value_to_int32(--value);
    y1 = value_to_int32(--value);
    x2 = value_to_int32(--value);
    y2 = value_to_int32(--value);

    XDrawLine(global_display, drawable, state->gc, x1, y1, x2, y2);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
draw_rect(eint32_t count)
/*
 *	void draw_rect(context_t, x, y, width, height)
 */
{
    eint32_t	 x;
    eint32_t	 y;
    euint32_t	 width;
    euint32_t	 height;
    estate_t	*state;
    evalue_t	*value;
    econtext_t	*context;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();

    x = value_to_int32(--value);
    y = value_to_int32(--value);
    width = value_to_int32(--value);
    height = value_to_int32(--value);

    XDrawRectangle(global_display, drawable, state->gc, x, y, width, height);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
fill_rect(eint32_t count)
/*
 *	void fill_rect(context_t, x, y, width, height)
 */
{
    eint32_t	 x;
    eint32_t	 y;
    euint32_t	 width;
    euint32_t	 height;
    estate_t	*state;
    evalue_t	*value;
    econtext_t	*context;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();

    x = value_to_int32(--value);
    y = value_to_int32(--value);
    width = value_to_int32(--value);
    height = value_to_int32(--value);

    XFillRectangle(global_display, drawable, state->gc, x, y, width, height);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
clear_rect(eint32_t count)
/*
 *	bool clear_rect(window_t, x, y, width, height, expose)
 */
{
    eint32_t	 x;
    eint32_t	 y;
    euint32_t	 width;
    euint32_t	 height;
    ewindow_t	*window;
    evalue_t	*value;
    ebool_t	 expose;

    window_lock();
    value = thread_self->esp - 1;
    if (value->t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = value->v.o;
    if (window->__window != None) {
	x = value_to_int32(--value);
	y = value_to_int32(--value);
	width = value_to_int32(--value);
	height = value_to_int32(--value);
	expose = value_to_int32(--value);

	XClearArea(global_display, window->__window,
		   x, y, width, height, expose);
	thread_self->ret.v.i = true;
    }
    else
	thread_self->ret.v.i = false;
    thread_self->ret.t = t_int;
    window_unlock();
}

static void
fill_poly(eint32_t count)
/*
 *	void fill_poly(context_t, int16_t[])
 */
{
    estate_t	*state;
    evalue_t	*value;
    evector_t	*vector;
    econtext_t	*context;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();
    thread_self->ret.t = t_void;
    switch ((--value)->t) {
	case t_void:
	    return;
	case t_vector|t_int16:
	    vector = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    XFillPolygon(global_display, drawable, state->gc, (XPoint *)vector->v.obj,
		 vector->length / 2, Complex, CoordModeOrigin);
    window_unlock();
}

static void
draw_text(eint32_t count)
/*
 *	void draw_text(context_t, x, y, string_t, [offset, length])
 */
{
    eint32_t	 x;
    eint32_t	 y;
    evalue_t	*value;
    estate_t	*state;
    eint32_t	 offset;
    eint32_t	 length;
    evector_t	*vector;
    econtext_t	*context;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();				--count;

    x = value_to_int32(--value);		--count;
    y = value_to_int32(--value);		--count;

    --value;					--count;
    switch (value->t & ~t_const) {
	case t_vector|t_int8:
	case t_vector|t_uint8:
	    vector = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    if (count > 0) {
	offset = value_to_int32(--value);	--count;
	/* allow offset == vector->length as length will be zero below;
	 * this simplifies interpreter code where offset is the insert
	 * position, or just the special case of an empty text string */
	if (offset < 0 || offset > vector->length) {
	    thread_self->except = except_out_of_bounds;
	    window_unlock();
	    return;
	}
    }
    else
	offset = 0;

    if (count > 0) {
	length = value_to_int32(--value);
	if (length < 0) {
	    thread_self->except = except_invalid_argument;
	    window_unlock();
	    return;
	}
    }
    else
	length = vector->length;

    if (offset + length > vector->length)
	length = vector->length - offset;

    XftDrawStringUtf8(state->draw, &state->foreground, state->font,
		      x, y + state->font->ascent,
		      vector->v.u8 + offset, length);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
text_rect(eint32_t count)
/*
 *	rect_t text_rect(context_t, string_t, [rect_t, offset, length])
 */
{
    erect_t	*rect;
    evalue_t	*value;
    estate_t	*state;
    eint32_t	 offset;
    eint32_t	 length;
    evector_t	*vector;
    econtext_t	*context;
    XGlyphInfo	 extents;
    Drawable	 drawable;

    window_lock();
    value = thread_self->esp - 1;
    validate_context();				--count;

    --value;					--count;
    switch (value->t & ~t_const) {
	case t_vector|t_int8:
	case t_vector|t_uint8:
	    vector = value->v.o;
	    break;
	default:
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
    }

    rect = null;
    if (count > 0) {
	--value;				--count;
	if (value->t != t_void) {
	    if (value->t != t_rect) {
		thread_self->except = except_type_mismatch;
		window_unlock();
		return;
	    }
	    rect = value->v.o;
	}
    }
    if (rect == null) {
	enew_object(&thread_self->obj, t_rect, sizeof(erect_t));
	rect = thread_self->obj;
    }

    if (count > 0) {
	offset = value_to_int32(--value);	--count;
	/* allow offset == vector->length as length will be zero below;
	 * this simplifies interpreter code where offset is the insert
	 * position, or just the special case of an empty text string */
	if (offset < 0 || offset > vector->length) {
	    thread_self->except = except_out_of_bounds;
	    window_unlock();
	    return;
	}
    }
    else
	offset = 0;

    if (count > 0) {
	length = value_to_int32(--value);
	if (length < 0) {
	    thread_self->except = except_invalid_argument;
	    window_unlock();
	    return;
	}
    }
    else
	length = vector->length;

    if (offset + length > vector->length)
	length = vector->length - offset;

    XftTextExtentsUtf8(global_display, state->font,
		       vector->v.u8 + offset, length, &extents);

    rect->x		 = 0;
    rect->y		 = state->font->ascent;
    rect->width		 = extents.xOff;
    rect->height	 = state->font->height;
    thread_self->ret.t	 = t_rect;
    thread_self->ret.v.o = rect;
    window_unlock();
}

static eint32_t
value_to_int32(evalue_t *value)
{
    switch (value->t) {
	case t_void:
	    return (0);
	case t_int:
	    return (value->v.i);
	case t_float:
	    return (value->v.d);
	case t_shz:	case t_mpz:
	    return (empz_get_i(value->v.o));
	    break;
	case t_shq:	case t_mpq:
	    return (mpq_get_d(value->v.o));
	case t_shr:	case t_mpr:
	    return (empr_get_i(value->v.o));
	case t_cdd:
	    return (real(value->v.cdd));
	case t_sqq:	case t_cqq:
	    return (mpq_get_d(cqq_realref(value->v.cqq)));
	case t_shc:	case t_mpc:
	    return (empr_get_i(mpc_realref(value->v.mpc)));
	default:
	    /* assume boolean - don't return -1 because
	     * it may "map" to a 0xffff dimension */
	    return (1);
    }
}

static void
pixel_to_color(euint32_t pixel, XRenderColor *color)
{
    eint32_t	component;

    /* use inverted alpha specification */
    component	 = 0xff - ((pixel >> 24) & 0xff);
    color->alpha = (component << 8) | component;
    component	 = (pixel >> 16)	 & 0xff;
    color->red	 = (component << 8) | component;
    component	 = (pixel >> 8)	& 0xff;
    color->green = (component << 8) | component;
    component	 = pixel		 & 0xff;
    color->blue	 = (component << 8) | component;
}

/***********************************************************************
 * Timer operations
 ***********************************************************************/
static void
create_timer(eint32_t count)
/*
 *	void create_timer(timer_t)
 */
{
    struct timeval	 now;
    etimer_t		*timer;

    window_lock();
    if (thread_self->esp[-1].t != t_timer) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    timer = thread_self->esp[-1].v.o;
    timer->__window = timer->window;
    timer->__msec = timer->msec;
    timer->__repeat = timer->repeat;
    timer->__next = root_timer;
    if (root_timer == null)
	root_timer = timer->__next = timer->__prev = timer;
    else {
	timer->__next = root_timer;
	timer->__prev = root_timer->__prev;
	root_timer->__prev = timer;
    }
    gettimeofday(&now, null);
    if (!update_timer(&now, timer)) {
	remove_timer(timer);
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    timer->pending = true;
    window_unlock();
}

static void
change_timer(eint32_t count)
/*
 *	bool change_timer(timer_t)
 */
{
    struct timeval	 now;
    etimer_t		*timer;

    window_lock();
    if (thread_self->esp[-1].t != t_timer) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    timer = thread_self->esp[-1].v.o;
    thread_self->ret.t = t_int;
    thread_self->ret.v.i = false;
    if (timer->__window != timer->window) {
	timer->__window = timer->window;
	thread_self->ret.v.i = true;
    }
    if (timer->__msec != timer->msec) {
	timer->__msec = timer->msec;
	gettimeofday(&now, null);
	if (!update_timer(&now, timer)) {
	    remove_timer(timer);
	    thread_self->except = except_type_mismatch;
	    window_unlock();
	    return;
	}
	thread_self->ret.v.i = true;
    }
    if (timer->__repeat != timer->repeat) {
	timer->__repeat = timer->repeat;
	thread_self->ret.v.i = true;
    }
    timer->pending = true;
    window_unlock();
}

static void
destroy_timer(eint32_t count)
/*
 *	bool destroy_timer(timer_t)
 */
{
    window_lock();
    if (thread_self->esp[-1].t != t_timer) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    thread_self->ret.t = t_int;
    thread_self->ret.v.i = remove_timer(thread_self->esp[-1].v.o);
    window_unlock();
}

static ebool_t
remove_timer(etimer_t *timer)
{
    if (timer->__next == timer) {
	if (root_timer)
	    root_timer = curr_timer = pend_timer = next_timer = null;
    }
    else if (timer->__next) {
	if (timer == curr_timer)
	    curr_timer = curr_timer->__next;
	if (timer == pend_timer)
	    pend_timer = null;
	if (timer == next_timer)
	    next_timer = null;
	timer->__next->__prev = timer->__prev;
	timer->__prev->__next = timer->__next;
    }

    if (timer->pending) {
	timer->pending = false;
	return (true);
    }

    return (false);
}

static ebool_t
update_timer(struct timeval *now, etimer_t *timer)
{
    struct timeval	next;

    if (timer->msec == 0)
	return (false);
    next.tv_sec = timer->msec / 1000;
    next.tv_usec = (timer->msec % 1000) * 1000;
    timeradd(now, &next, &timer->__tv);

    return (true);
}

static ebool_t
adjust_timers(struct timeval *now)
{
    etimer_t	*timer;

    /* if no timers installed */
    if (root_timer == null)
	return (false);

    /* check if there is any pending timer */
    if (pend_timer)
	return (true);

    /* fast check if previous value of next_timer is already elapsed */
    if (next_timer) {
	if (timercmp(now, &next_timer->__tv, >=)) {
	    pend_timer = curr_timer = next_timer;
	    next_timer = null;
	    return (true);
	}
    }

    /* use curr_timer to avoid serving only one timer
     * if it takes too long to get back here */
    if (curr_timer == null)
	curr_timer = root_timer;
    timer = curr_timer;
    do {
	if (timercmp(now, &timer->__tv, >=)) {
	    pend_timer = curr_timer = timer;
	    return (true);
	}
	timer = timer->__next;
    } while (timer != curr_timer);
    /* no pending timers */
    pend_timer = null;

    /* check if need to update next timer to trigger */
    if (next_timer)
	return (false);
    timer = curr_timer;
    do {
	if (timercmp(now, &timer->__tv, <)) {
	    if (next_timer) {
		if (timercmp(&timer->__tv, &next_timer->__tv, <))
		    next_timer = timer;
	    }
	    else
		next_timer = timer;
	}
	timer = timer->__next;
    } while (timer != curr_timer);

    /* update restart point */
    if (next_timer)
	curr_timer = next_timer;

    return (false);
}

static ebool_t
timer_event(eevent_t *event, ebool_t block)
{
    struct timeval	now;
    fd_set		set;

    if (!root_timer)
	return (false);

    for (;;) {
	/* only process timers when there are no pending events */
	if (XPending(global_display))
	    return (false);

	gettimeofday(&now, null);

	/* if no pending timer */
	if (!adjust_timers(&now)) {
	    /* if can block and there is a next timer */
	    if (block && next_timer) {
		FD_ZERO(&set);
		FD_SET(ConnectionNumber(global_display), &set);
		timersub(&next_timer->__tv, &now, &now);
		if (select(FD_SETSIZE, &set, null, null, &now) == 0)
		    /* if timer will become available */
		    continue;
	    }

	    /* no timer pending */
	    return (false);
	}

	/* process pending timer */
	event->type	= etimer_event;
	event->window	= (ewindow_t *)pend_timer;
	event->x	= 0;
	event->x	= 0;
	event->width	= 0;
	event->height	= 0;
	event->state	= 0;
	event->detail	= 0;
	if (pend_timer->repeat)
	    update_timer(&now, pend_timer);
	else
	    remove_timer(pend_timer);
	pend_timer = null;

	return (true);
    }
}

/***********************************************************************
 * (simple) Selection operations
 ***********************************************************************/
static void
fetch_bytes(eint32_t count)
{
    char	*bytes;
    int		 length;
    evector_t	*vector;

    window_lock();
    bytes = XFetchBytes(global_display, &length);
    enew_vector((evector_t **)&thread_self->obj, t_uint8, length, 1);
    vector = thread_self->obj;
    memcpy(vector->v.obj, bytes, length);
    XFree(bytes);

    thread_self->ret.t = t_vector|t_uint8;
    thread_self->ret.v.o = thread_self->obj;
    window_unlock();
}

static void
store_bytes(eint32_t count)
{
    evalue_t	*value;
    eint32_t	 offset;
    eint32_t	 length;
    evector_t	*vector;

    window_lock();
    value = thread_self->esp - 1;		--count;
    if ((value->t & ~t_const) == (t_vector|t_uint8))
	vector = value->v.o;
    else {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    if (count > 0) {
	offset = value_to_int32(--value);	--count;
	if (offset < 0 || offset > vector->length) {
	    thread_self->except = except_out_of_bounds;
	    window_unlock();
	    return;
	}
    }
    else
	offset = 0;

    if (count > 0) {
	length = value_to_int32(--value);	--count;
	if (length < 0) {
	    thread_self->except = except_invalid_argument;
	    window_unlock();
	    return;
	}
    }
    else
	length = vector->length;
    if (offset + length > vector->length)
	length = vector->length - offset;
    XStoreBytes(global_display, ((char *)vector->v.obj) + offset, length);
    thread_self->ret.t = t_void;
    window_unlock();
}

/***********************************************************************
 * (simple) Grab operations
 ***********************************************************************/
static void
grab_input(eint32_t count)
{
    evalue_t	*value;
    ewindow_t	*window;

    window_lock();
    value = thread_self->esp - 1;
    if (value->t != t_window) {
	thread_self->except = except_type_mismatch;
	window_unlock();
	return;
    }
    window = value->v.o;
    XGrabPointer(global_display, window->__window, True,
		 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
		 EnterWindowMask | LeaveWindowMask, GrabModeAsync,
		 GrabModeAsync, None, None, CurrentTime);
    XGrabKeyboard(global_display, window->__window, True,
		  GrabModeAsync, GrabModeAsync, CurrentTime);
    thread_self->ret.t = t_void;
    window_unlock();
}

static void
ungrab_input(eint32_t count)
{
    window_lock();
    XUngrabPointer(global_display, CurrentTime);
    XUngrabKeyboard(global_display, CurrentTime);
    thread_self->ret.t = t_void;
    window_unlock();
}
